User._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
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: User.pike,v 1.7 2010/01/25 19:18:18 astra Exp $
18  */
19 inherit "/classes/Container" : __cont;
20 inherit "/base/member" : __member;
21 #include <attributes.h>
22 #include <assert.h>
23 #include <macros.h>
24 #include <events.h>
25 #include <coal.h>
26 #include <classes.h>
27 #include <database.h>
28 #include <access.h>
29 #include <types.h>
30 #include <client.h>
31 #include <config.h>
32 #include <exception.h>
33 //! this is the user object. It keeps track of connections and membership
34 //! in groups.
35 class User : public Container,member{
36 public:
37 
38 
39 
40 
41 
42 
43 
44 //#define EVENT_USER_DEBUG
45 
46 #ifdef EVENT_USER_DEBUG
47 #define DEBUG_EVENT(s, args...) werror(s+"\n", args)
48 #else
49 #define DEBUG_EVENT(s, args...)
50 #endif
51 
52 /* Security relevant functions */
53 private string sUserPass; // the password for the user
54 private string sPlainPass;
55 private string sUserName; // the name of the user
56 private object oActiveGrp; // the active group
57 private int iCommandTime; // when the last command was send
58 
59 private mapping mAttributeAccess = ([ ]); // set readable
60 
61 private string sTicket;
62 private array aTickets;
63 private int iActiveCode;
64 
65  mapping mSockets;
66  mapping mMoveEvents;
67 private mapping mSocketEvents;
68  mapping mVirtualConnections;
69 
70 bool userLoaded = false;
71 
72  Thread.Mutex annotationMutex = Thread.Mutex();
73 
74 bool check_swap() { return false; }
75 bool check_upgrade() { return false; }
76 
77 protected:
78  void
79 init()
80 {
81  ::init();
82  ::init_member();
83  mSockets = ([ ]);
84  mSocketEvents = ([ ]);
85  mVirtualConnections = ([ ]);
86  sTicket = 0;
87 
88  /* the user name is a locked attribute */
89  add_data_storage(STORE_USER, store_user_data, restore_user_data, 1);
90 }
91 
92 public:
93 
94 /**
95  * Constructor of the user object.
96  *
97  */
98 protected:
99  void
100 create_object()
101 {
102  ::create_object();
103 
104  sUserName = "noone";
105  sUserPass = "steam";
106  sPlainPass = 0;
107 
108  sTicket = 0;
109  aTickets = ({ });
110  mAttributeAccess = ([ ]);
111  iActiveCode = 0;
112 }
113 
114 public:
115 
116 /**
117  * Creating a duplicate of the user wont work.
118  *
119  * @return throws an error
120  */
121 object duplicate(void|mapping vars)
122 {
123  THROW("User cannot be duplicated !\n", E_ERROR);
124 }
125 
126 /**
127  * register the object in the database.
128  *
129  * @param name - the name of the object
130  */
131 protected:
132  void database_registration(string name)
133 {
134 }
135 
136 public:
137 
138 /**
139  * Destructor of the user.
140  *
141  * @see create
142  */
143 protected:
144  void
145 delete_object()
146 {
147  mixed err;
148 
149  THROW("Cannot delete the root user !", E_ACCESS);
150 
151  WARN("DELETING user %O by %O through %O", sUserName, this_user(), CALLER);
152  object mailbox = do_query_attribute(USER_MAILBOX);
153  // delete the mailbox recursively
154  if ( objectp(mailbox) ) {
155  foreach(mailbox->get_inventory(), object inv) {
156  err = catch {
157  inv->delete();
158  };
159  }
160  err = catch {
161  mailbox->delete();
162  };
163  }
164  err = catch {
165  object workroom = do_query_attribute(USER_WORKROOM);
166  if ( objectp(workroom) ) workroom->delete();
167  };
168  if ( err != 0 )
169  FATAL( "Failed to delete workroom of \"%s\": %O\n%O\n", sUserName, err[0], err[1] );
170  err = catch {
171  object bookmarks = do_query_attribute(USER_BOOKMARKROOM);
172  if ( objectp(bookmarks) ) bookmarks->delete();
173  };
174  if ( err != 0 )
175  FATAL( "Failed to delete bookmars of \"%s\": %O\n%O\n", sUserName, err[0], err[1] );
176  err = catch {
177  object calendar = do_query_attribute(USER_CALENDAR);
178  if ( objectp(calendar) ) calendar->delete();
179  };
180  if ( err != 0 )
181  FATAL( "Failed to delete calendar of \"%s\": %O\n%O\n", sUserName, err[0], err[1] );
182 
183  __member::delete_object();
184  __cont::delete_object();
185 }
186 
187 public:
188 
189 /**
190  * Dont update a users name.
191  */
192 void update_identifier()
193 {
194 }
195 
196 /**
197  * Dont update a users path (its ~username anyway)
198  */
199 void update_path()
200 {
201 }
202 
203 /**
204  * Create all the exits to the groups the user is member of.
205  *
206  */
207 void create_group_exits()
208 {
209  object workroom = do_query_attribute(USER_WORKROOM);
210  if ( objectp(workroom) ) {
211  array inv = workroom->get_inventory();
212  array groups = get_groups();
213  mapping mExits = ([ ]);
214 
215  foreach ( groups, object grp ) {
216  if ( !objectp(grp) ) continue;
217  mapping exits = grp->query_attribute(GROUP_EXITS);
218  if ( !mappingp(exits) ) {
219  object workroom = grp->query_attribute(GROUP_WORKROOM);
220  exits = ([ workroom: workroom->get_identifier(), ]);
221  }
222  mExits += exits;
223  }
224  foreach ( indices(mExits), object exit ) {
225  bool found_exit;
226 
227  if ( !objectp(exit) )
228  continue;
229  found_exit = false;
230  foreach ( inv, object o ) {
231  if ( o->get_object_class() & CLASS_EXIT ) {
232  object exit_to = o->get_link_object();
233  if ( !objectp(exit_to) )
234  continue;
235  if ( exit_to->get_object_id() == exit->get_object_id() )
236  found_exit = true;
237  }
238  }
239  if ( !found_exit ) {
240  object factory = _Server->get_factory(CLASS_EXIT);
241  object exit = factory->execute(
242  ([ "name": mExits[exit], "exit_to": exit, ]) );
243  exit->move(workroom);
244  }
245  }
246  }
247 }
248 
249 protected:
250  string new_session_id()
251 {
252  string sid;
253 #if constant(Crypto.Random)
254  sid = sprintf("%x", hash(Crypto.Random.random_string(10)));
255 #else
256  sid = sprintf("%x", hash(random(1000000) + time() + sUserName+sUserPass));
257 #endif
258  return sid;
259 }
260 
261 public:
262 
263 /**
264  * Connect the user object to a steamsocket.
265  *
266  * @param obj - the steamsocket to connect to
267  * @return the time of the last login
268  * @see disconnect
269  * @see which_socket
270  */
271 int
272 connect(object obj)
273 {
274  int last_login, i;
275 
276  LOG("Connecting "+ get_identifier()+" with "+ obj->describe()+"\n");
277 
278  if ( !IS_SOCKET(CALLER) )
279  THROW("Trying to connect user to non-steamsocket !", E_ACCESS);
280 
281  array aoSocket = values(mSockets);
282  for ( i = sizeof(aoSocket) - 1; i >= 0; i-- ) {
283  if ( aoSocket[i] == obj )
284  return 0;
285  }
286  int features = obj->get_client_features();
287  int prev_features = get_status();
288 
289 
290  string sid = new_session_id();
291  while ( objectp(mSockets[sid]) || objectp(mVirtualConnections[sid]) )
292  sid = new_session_id();
293  mSockets[sid] = obj;
294  mSockets[obj] = sid;
295 
296  m_delete(mSockets, 0);
297  foreach ( indices(mSockets), sid)
298  if ( !objectp(mSockets[sid]) && !stringp(mSockets[sid]) )
299  m_delete(mSockets, sid);
300 
301  last_login = do_query_attribute(USER_LAST_LOGIN);
302  do_set_attribute(USER_LAST_LOGIN, time());
303 
304  if ( (prev_features & features) != features )
305 
306  return last_login;
307 }
308 
309 /**
310  * Connect the user object to a virtual connection.
311  *
312  * @param obj - the virtual connection to connect to
313  * @return the time of the last login
314  * @see disconnect_virtual
315  */
316 int connect_virtual ( object connection ) {
317  int last_login;
318  LOG( "Connecting (virtual) " + get_identifier() + " with "
319  + connection->describe() + "\n" );
320  if ( has_value( mVirtualConnections, connection ) )
321  return 0;
322  int features = connection->get_client_features();
323  int prev_features = get_status();
324 
325  string sid = new_session_id();
326  while ( objectp(mSockets[sid]) || objectp(mVirtualConnections[sid]) )
327  sid = new_session_id();
328  mVirtualConnections[ sid ] = connection;
329  mVirtualConnections[ connection ] = sid;
330 
331  m_delete( mVirtualConnections, 0 );
332  foreach ( indices(mVirtualConnections), sid )
333  if ( !objectp(mVirtualConnections[sid]) &&
334  !stringp(mVirtualConnections[sid]) )
335  m_delete( mVirtualConnections, sid );
336 
337  last_login = do_query_attribute( USER_LAST_LOGIN );
338  do_set_attribute( USER_LAST_LOGIN, time() );
339 
340  if ( (prev_features & features) != features )
341 
342  return last_login;
343 }
344 
345 string get_session_id()
346 {
347  if ( !IS_SOCKET(CALLER) )
348  THROW("Trying to steal session by non-socket !", E_ACCESS);
349  foreach( indices(mSockets), string sid) {
350  if ( mSockets[sid] == CALLER )
351  return sid;
352  }
353  return "0";
354 }
355 
356 string get_virtual_session_id () {
357  mixed sid = mVirtualConnections[ CALLER ];
358  if ( stringp(sid) ) return sid;
359  return "0";
360 }
361 
362 bool join_group(object grp)
363 {
364  try_event(EVENT_USER_JOIN_GROUP, CALLER, grp);
365  mixed res = ::join_group(grp);
366  require_save(STORE_USER);
367  run_event(EVENT_USER_JOIN_GROUP, CALLER, grp);
368  return res;
369 }
370 
371 bool leave_group(object grp)
372 {
373  try_event(EVENT_USER_LEAVE_GROUP, CALLER, grp);
374  mixed res = ::leave_group(grp);
375  run_event(EVENT_USER_LEAVE_GROUP, CALLER, grp);
376  return res;
377 }
378 
379 /**
380  * Close the connection to socket and logout.
381  *
382  * @param obj - the object to remove from active socket list
383  * @see disconnect
384  */
385 protected:
386  void
387 close_connection(object obj)
388 {
389  if ( which_socket(obj) < 0 ) return;
390 
391  try_event(EVENT_LOGOUT, CALLER, obj);
392 
393  foreach(indices(mSockets), string sid)
394  if ( mSockets[sid] == obj )
395  m_delete(mSockets, sid);
396 
397  int cfeatures = obj->get_client_features();
398  int features = get_status();
399 
400  if ( (cfeatures & features) != cfeatures )
401 
402  ASSERTINFO(which_socket(obj) < 0, "Still connected to socket !");
403  DEBUG_EVENT(sUserName+": logout event....");
404  run_event(EVENT_LOGOUT, CALLER, obj);
405 }
406 
407 public:
408 
409 /**
410  * Close the connection to a virtual connection and logout.
411  *
412  * @param obj - the object to remove from active virtual connection list
413  * @see disconnect_virtual
414  */
415 protected:
416  void close_virtual_connection ( object connection ) {
417  if ( !has_value( mVirtualConnections, connection ) ) return;
418 
419  try_event( EVENT_LOGOUT, CALLER, connection );
420 
421  m_delete( mVirtualConnections, mVirtualConnections[connection] );
422  m_delete( mVirtualConnections, connection );
423 
424  int cfeatures = connection->get_client_features();
425  int features = get_status();
426 
427  if ( (cfeatures & features) != cfeatures )
428 
429  ASSERTINFO( !has_value( mVirtualConnections, connection),
430  "Still connected to virtual connection !" );
431  DEBUG_EVENT( sUserName+": logout event...." );
432  run_event( EVENT_LOGOUT, CALLER, connection );
433 }
434 
435 public:
436 
437 /**
438  * Disconnect the CALLER socket from this user object.
439  *
440  * @see connect
441  */
442 void disconnect()
443 {
444  object socket = CALLER;
445  int status;
446 
447  if ( which_socket(socket) == -1 )
448  return;
449 
450  if ( arrayp(mSocketEvents[socket]) ) {
451  foreach ( mSocketEvents[socket], mixed event_data )
452  if ( arrayp(event_data) )
453  remove_event(@event_data);
454  }
455  // get the remaining status of the user
456  status = 0;
457  array aoSocket = values(mSockets);
458  foreach ( aoSocket, mixed sock ) {
459  if ( objectp(sock) && sock != socket ) {
460  status |= sock->get_client_features();
461  }
462  }
463  foreach ( values(mVirtualConnections), object conn ) {
464  if ( objectp(conn) )
465  status |= conn->get_client_features();
466  }
467 
468 #ifdef MOVE_WORKROOM
469  // if this is a client which allows movement of the user
470  // then move the user back to its workroom
471  if ( !(status & CLIENT_FEATURES_MOVE) )
472  {
473  object workroom = do_query_attribute(USER_WORKROOM);
474  if ( oEnvironment != workroom ) {
475  LOG("Closing down connection to user - moving to workroom !");
476  do_set_attribute(USER_LOGOUT_PLACE, oEnvironment);
477  if ( objectp(workroom) )
478  move(workroom);
479  }
480  }
481 #endif
482  close_connection(socket);
483 }
484 
485 /**
486  * Disconnect the CALLER virtual connection from this user object.
487  *
488  * @see connect_virtual
489  */
490 void disconnect_virtual () {
491  object connection = CALLER;
492 
493  if ( !has_value( mVirtualConnections, connection) )
494  return;
495 
496  // get the remaining status of the user
497  int status = 0;
498  foreach ( values(mSockets), object sock ) {
499  if ( objectp(sock) )
500  status |= sock->get_client_features();
501  }
502  foreach ( values(mVirtualConnections), object conn ) {
503  if ( objectp(conn) && conn != connection )
504  status |= conn->get_client_features();
505  }
506 
507 #ifdef MOVE_WORKROOM
508  // if this is a client which allows movement of the user
509  // then move the user back to its workroom
510  if ( !(status & CLIENT_FEATURES_MOVE) ) {
511  object workroom = do_query_attribute( USER_WORKROOM );
512  if ( oEnvironment != workroom ) {
513  LOG("Closing down connection to user - moving to workroom !");
514  do_set_attribute( USER_LOGOUT_PLACE, oEnvironment );
515  if ( objectp(workroom) )
516  move( workroom );
517  }
518  }
519 #endif
520  close_virtual_connection( connection );
521 }
522 
523 /**
524  * find out if the object is one of the connected sockets
525  *
526  * @param obj - the object to find out about
527  * @return the position of the socket in the socket array
528  * @see connect
529  * @see disconnect
530  */
531 protected:
532  int
533 which_socket(object obj)
534 {
535  return search(values(mSockets), obj);
536 }
537 
538 public:
539 
540 /**
541  * Activate the login. Successfull activation code is required to do so!
542  *
543  * @param int activation - the activation code
544  * @return true or false
545  */
546 bool activate_user(int|void activation)
547 {
548  if ( activation == iActiveCode || _ADMIN->is_member(this_user()) ) {
549  iActiveCode = 0;
550  require_save(STORE_USER);
551  return true;
552  }
553  return false;
554 }
555 
556 /**
557  * Set the activation code for an user - this is done by the factory.
558  *
559  * @param int activation - the activation code.
560  * @see activate_user
561  */
562 void set_activation(int activation)
563 {
564  if ( CALLER != _Server->get_factory(CLASS_USER) &&
565  !_ADMIN->is_member(this_user()) )
566  THROW("Invalid call to set_activation !", E_ACCESS);
567  iActiveCode = activation;
568  require_save(STORE_USER);
569 }
570 
571 /**
572  * Find out if the user is inactivated.
573  *
574  * @return activation code set or not.
575  */
576 bool get_activation()
577 {
578  return iActiveCode != 0;
579 }
580 
581 /**
582  * Check if a given password is correct. Users can authenticate with their
583  * password or with temporary tickets. There are one time tickets and
584  * tickets which last for acertain time encoded in the ticket itself.
585  * Authentication will always fail if the user is not activated.
586  *
587  * @param pw - the password to check
588  * @param uid - the user object
589  * @return if the password matches or not
590  */
591 bool check_user_password(string pw)
592 {
593  if ( !stringp(sUserPass) ) {
594  if ( get_module("auth")->allow_zero_passwords() )
595  return true;
596  }
597 
598  if ( !stringp(pw) )
599  return false;
600 
601  if ( iActiveCode ) {
602  MESSAGE("Trying to authenticate with inactivated user !");
603  return false; // as long as the login is not activated
604  }
605 
606  if ( stringp(sTicket) )
607  {
608  if ( verify_crypt_md5(pw, sTicket) ) {
609  sTicket = 0; // ticket used
610  return true;
611  }
612  }
613  if ( arrayp(aTickets) && sizeof(aTickets) > 0 ) {
614  array tickets = copy_value(aTickets);
615  foreach(tickets, string ticket) {
616  int t;
617  sscanf(ticket, "%*s_%d", t);
618  if ( t < time() ) {
619  aTickets -= ({ ticket });
620  require_save(STORE_USER);
621  }
622  else if ( pw == ticket )
623  return true;
624  }
625  }
626  // allow login with any session ID from a connected socket
627  foreach ( indices(mSockets), string sid)
628  if ( pw == sid )
629  return true;
630 
631  if ( !stringp(sUserPass) && !get_module("auth")->allow_zero_passwords() )
632  return false;
633 
634  if ( strlen(sUserPass) > 5 && lower_case(sUserPass[0..4]) == "{sha}" )
635  return sUserPass[5..] == MIME.encode_base64( sha_hash(pw) );
636  if ( strlen(sUserPass) > 6 && lower_case(sUserPass[0..5]) == "{ssha}" ) {
637  string salt = MIME.decode_base64( sUserPass[6..] )[20..]; // last 8 bytes is the salt
638  return sUserPass[6..] == MIME.encode_base64( sha_hash(pw+salt) );
639  }
640  if ( strlen(sUserPass) > 7 && lower_case(sUserPass[0..6]) == "{crypt}" )
641  return crypt(pw, sUserPass[7..]);
642  if ( strlen(sUserPass) > 4 && lower_case(sUserPass[0..3]) == "{lm}" ) {
643  return sUserPass[4..] == LanManHash.lanman_hash(pw);
644  }
645  if ( strlen(sUserPass) < 3 || sUserPass[0..2] != "$1$" )
646  return crypt(pw, sUserPass); // normal crypt check
647 
648  return verify_crypt_md5(pw, sUserPass);
649 }
650 
651 /**
652  * Get a ticket from the server - authenticate to the server with
653  * this ticket once. Optional parameter t gives time the ticket
654  * is valid.
655  *
656  * @param void|int t - the validity of the ticket
657  * @return the ticket
658  * @see check_user_password
659  */
660 final string get_ticket(void|int t)
661 {
662  THROW("Invalid call to get_ticket() !", E_ACCESS);
663 
664  try_event(EVENT_USER_NEW_TICKET, CALLER, 0);
665 
666  string ticket = " ";
667  for ( int i = 0; i < 8; i++ )
668  ticket[i] = random(26) + 'a';
669  ticket = crypt(ticket + time());
670  ticket = String.string2hex(ticket);
671  if ( !zero_type(t) ) {
672  ticket += "_" + t;
673  if(arrayp(aTickets))
674  aTickets += ({ ticket });
675  else
676  aTickets = ({ ticket });
677  run_event(EVENT_USER_NEW_TICKET, CALLER, "********");
678  require_save(STORE_USER);
679  return ticket;
680  }
681 
682  sTicket = make_crypt_md5(ticket);
683  run_event(EVENT_USER_NEW_TICKET, CALLER, "*********");
684  return ticket;
685 }
686 
687  string oldpassword;
688 /**
689  * temporary storage for old password while password is being changed.
690  * to allow places like ldap to pick get the old password, in case they need it
691  * to set the new one.
692  * @return oldpassword
693  * @see check_user_pasword
694  */
695 string get_old_password()
696 {
697  THROW(sprintf("%O is not permitted to read the old password!", CALLER),
698  E_ACCESS);
699  //werror("get_old_password: %O\n", this_user());
700  return oldpassword;
701 }
702 
703 /**
704  * Set the user password and save an md5 hash of it.
705  *
706  * @param pw - the new password for the user
707  * @return if successfull
708  * @see check_user_pasword
709  */
710 bool
711 set_user_password(string pw, int|void crypted, string|void oldpw)
712 {
713  oldpassword=oldpw;
714  try_event(EVENT_USER_CHANGE_PW, CALLER);
715  if(crypted)
716  sUserPass = pw;
717  else
718  sUserPass = make_crypt_md5(pw);
719  require_save(STORE_USER);
720  run_event(EVENT_USER_CHANGE_PW, CALLER);
721  oldpassword=0;
722  return true;
723 }
724 
725 bool
726 set_user_password_plain(string pw, int|void crypted)
727 {
728  try_event(EVENT_USER_CHANGE_PW, CALLER);
729  if(crypted)
730  sPlainPass = pw;
731  else
732  sPlainPass = make_crypt_md5(pw);
733  require_save(STORE_USER);
734  run_event(EVENT_USER_CHANGE_PW, CALLER);
735  return true;
736 }
737 
738 
739 /**
740  * Get the password of the user which should be fine since
741  * we have an md5 hash. This is used to import/export users.
742  *
743  * @return the users password.
744  */
745 string
746 get_user_password(string|void pw)
747 {
748  // security problem ? ask for read permissions at least -
749  // probably for admin?
750  return copy_value(sUserPass);
751 }
752 
753 /**
754  * Get the user object of the user which is this object.
755  *
756  */
757 object get_user_object()
758 {
759 }
760 
761 /**
762  * Get the sTeam e-mail adress of this user. Usually its the users name
763  * on _Server->get_server_name() ( if sTeam runs smtp on port 25 )
764  *
765  * @return the e-mail adress of this user
766  */
767 string get_steam_email()
768 {
769  return sUserName + "@" + _Server->get_server_name();
770 }
771 
772 /**
773  * set the user name, which is only allowed for the factory.
774  *
775  * @param string name - the new name of the user.
776  */
777 void
778 set_user_name(string name)
779 {
780  if ( !_Server->is_factory(CALLER) && stringp(sUserName) )
781  THROW("Calling object not trusted !", E_ACCESS);
782  if ( !stringp(name) )
783  error("set_user_name(0) is not allowed!");
784 
785  string old_name = sUserName;
786 
787  sUserName = name;
788  do_set_attribute(OBJ_NAME, name);
789 
790  object workroom = do_query_attribute(USER_WORKROOM);
791  if ( objectp(workroom) ) {
792  if ( workroom->query_attribute(OBJ_NAME) == old_name+"'s workarea" )
793  workroom->set_attribute( OBJ_NAME, name + "'s workarea" );
794  else
795  workroom->update_path();
796  }
797 
798  require_save(STORE_USER);
799 }
800 
801 string
802 get_user_name()
803 {
804  return copy_value(sUserName);
805 }
806 
807 /**
808  * Get the complete name of the user, that is first and lastname.
809  * Last name attribute is called FULLNAME because of backwards compatibility.
810  *
811  * @return the first and last name
812  */
813 string get_name()
814 {
815  string lname, fname;
816  lname = do_query_attribute(USER_LASTNAME);
817  fname = do_query_attribute(USER_FIRSTNAME);
818  if ( !stringp(fname) )
819  return lname;
820 
821  return fname + " " + lname;
822 }
823 
824 
825 /**
826  * restore the use specific data
827  *
828  * @param data - the unserialized data of the user
829 private:
830  * @see store_user_data
831  */
832 void
833 private:
834 restore_user_data(mixed data, string|void index)
835 {
836  if ( CALLER != _Database )
837  THROW("Invalid call to restore_user_data()", E_ACCESS);
838 
839  if ( equal(data, ([ ])) ) {
840  FATAL("Empty load in restore_user_data()");
841  return;
842  }
843  if ( userLoaded && !stringp(index) )
844  steam_error("Loading already loaded user: " + sUserName + ":"+
845  get_object_id());
846  if (zero_type(index)) // no index set restore all
847  {
848  if ( !stringp(data->UserName) ) {
849  FATAL("In: " + get_object_id() + ": "+
850  "Cannot restore user with 0-name, already got " +
851  sUserName);
852  return;
853  }
854 
855  sUserName = data["UserName"];
856  sUserPass = data["UserPassword"];
857  sPlainPass = data["PlainPass"];
858  sTicket = data["UserTicket"];
859  if ( !stringp(sPlainPass) )
860  sPlainPass = "";
861  aoGroups = data["Groups"];
862  iActiveCode = data["Activation"];
863  aTickets = data["Tickets"];
864  oActiveGrp = data["ActiveGroup"];
865  mAttributeAccess = data["AttributeAccess"];
866  if ( !arrayp(aTickets) )
867  aTickets = ({ });
868  if (!mappingp(mAttributeAccess))
869  mAttributeAccess = ([ ]);
870  userLoaded = true;
871  }
872  else
873  {
874  switch(index) {
875  case "UserName" :
876  if ( !stringp(data) ) {
877  FATAL("In: " + get_object_id() +
878  " : Cannot restore user with null, previous name " +
879  sUserName);
880  return;
881  }
882  sUserName = data;
883  break;
884  case "UserPassword" : sUserPass = data; break;
885  case "PlainPass" :
886  if (stringp(data))
887  sPlainPass = data;
888  else
889  sPlainPass = "";
890  break;
891  case "AttributeAccess": mAttributeAccess = data; break;
892  case "UserTicket" : sTicket = data; break;
893  case "Groups" : aoGroups = data; break;
894  case "Activation" : iActiveCode = data; break;
895  case "ActiveGroup" : oActiveGrp = data; break;
896  case "Tickets" :
897  if (arrayp(aTickets))
898  aTickets = data;
899  else
900  aTickets = ({});
901  break;
902  }
903  }
904  //ASSERTINFO(arrayp(aoGroups),"Group is not an array !");
905  if ( !arrayp(aoGroups) )
906  aoGroups = ({ });
907 }
908 
909 public:
910 
911 /**
912  * returns the userdata that will be stored in the Database
913  *
914  * @return array containing user data
915 private:
916  * @see restore_user_data
917  */
918 final mixed
919 private:
920 store_user_data(string|void index)
921 {
922  if ( CALLER != _Database )
923  THROW("Invalid call to store_user_data()", E_ACCESS);
924 
925  if (zero_type(index))
926  {
927  return ([
928  "UserName":sUserName,
929  "UserPassword":sUserPass,
930  "PlainPass":sPlainPass,
931  "Groups": aoGroups,
932  "Activation": iActiveCode,
933  "Tickets": aTickets,
934  "ActiveGroup": oActiveGrp,
935  "UserTicket" : sTicket,
936  ]);
937  } else {
938  switch(index) {
939  case "UserName": return sUserName;
940  case "UserPassword": return sUserPass;
941  case "PlainPass": return sPlainPass;
942  case "Groups": return aoGroups;
943  case "Activation": return iActiveCode;
944  case "Tickets": return aTickets;
945  case "ActiveGroup": return oActiveGrp;
946  case "UserTicket" : return sTicket;
947  case "AttributeAccess": return mAttributeAccess;
948  default:
949  steam_error("Invalid index in store_user_data(%O)\n", index);
950  }
951  }
952 }
953 
954 public:
955 
956 /**
957  * the event listener function. The event is automatically send
958  * to the client.
959  *
960  * @param event - the type of event
961  * @param args - the different args for each event
962  * @return ok
963  * @see listen_event
964  */
965 final int notify_event(int event, mixed ... args)
966 {
967  int i;
968  array sockets;
969 
970  DEBUG_EVENT(sUserName+":notify_event("+event+",....)");
971  sockets = values(mSockets);
972 
973  if ( !arrayp(sockets) || sizeof(sockets) == 0 )
974  return EVENT_OK;
975 
976  for ( i = sizeof(sockets) - 1; i >= 0; i-- ) {
977  if ( objectp(sockets[i]) ) {
978  if ( !objectp(sockets[i]->_fd) ) {
979  LOG("Closing connection...\n");
980  close_connection(sockets[i]);
981  continue;
982  }
983  if ( sockets[i]->get_client_features() & CLIENT_FEATURES_EVENTS ){
984  LOG("Notifying socket " + i + " about event: " + event);
985  sockets[i]->notify(event, @args);
986  }
987  }
988  }
989  return EVENT_OK;
990 }
991 
992 protected:
993  bool do_add_annotation(object mail)
994 {
995  bool result = 0;
996 
997  object lock = annotationMutex->lock();
998  mixed err = catch {
999  object temp_objects = get_module("temp_objects");
1000  if (objectp(temp_objects)) {
1001  mixed mailtime = _Server->get_config("mail_expire");
1002  if ( !stringp(mailtime) && mailtime > 0 )
1003  temp_objects->add_temp_object(mail, time() + mailtime);
1004  }
1005  result = ::do_add_annotation(mail);
1006  };
1007  if (err) {
1008  destruct(lock);
1009  throw(err);
1010  }
1011  destruct(lock);
1012  return result;
1013 }
1014 
1015 public:
1016 
1017 /**
1018  * Get the annotations, eg e-mails of the user.
1019  *
1020  * @return list of annotations
1021  */
1022 array get_annotations()
1023 {
1024  object mb = do_query_attribute(USER_MAILBOX);
1025  if ( objectp(mb) ) {
1026  // import messages from mailbox
1027  foreach ( mb->get_inventory(), object importobj) {
1028  catch(add_annotation(importobj));
1029  importobj->set_acquire(0);
1030  }
1031  do_set_attribute(USER_MAILBOX, 0);
1032  }
1033  return ::get_annotations();
1034 }
1035 
1036 /**
1037  * Get the mails of a user.
1038  *
1039  * @return array of objects of mail documents
1040  */
1041 array get_mails(void|int from_obj, void|int to_obj)
1042 {
1043  array mails = get_annotations();
1044  if ( sizeof(mails) == 0 )
1045  return mails;
1046 
1047  if ( !intp(to_obj) )
1048  to_obj = sizeof(mails);
1049  if ( !intp(from_obj) )
1050  from_obj = 1;
1051  return mails[from_obj-1..to_obj-1];
1052 }
1053 
1054 
1055 /**
1056  * Returns the user's emails, optionally filtered by object class,
1057  * attribute values or pagination.
1058  * The description of the filters and sort options can be found in the
1059  * filter_objects_array() function of the "searching" module.
1060  *
1061  * Example:
1062  * Return the 10 newest mails whose subjects do not start with "{SPAM}",
1063  * sorted by date.
1064  * get_mails_filtered(
1065  * ({ // filters:
1066  * ({ "-", "attribute", "OBJ_DESC", "prefix", "{SPAM}" }),
1067  * ({ "+", "class", CLASS_DOCUMENT }),
1068  * }),
1069  * ({ // sort:
1070  * ({ ">", "attribute", "OBJ_CREATION_TIME" })
1071  * }), 0, 10 );
1072  *
1073  * @param mail_folder (optional) mail folder from which to return the mails
1074  * (if not specified, then the inbox of the user is used)
1075  * @param filters (optional) an array of filters (each an array as described
1076  * in the "searching" module) that specify which objects to return
1077  * @param sort (optional) an array of sort entries (each an array as described
1078  * in the "searching" module) that specify the order of the items
1079  * @param offset (optional) only return the objects starting at (and including)
1080  * this index
1081  * @param length (optional) only return a maximum of this many objects
1082  * @return a mapping ([ "objects":({...}), "total":nr, "length":nr,
1083  * "start":nr, "page":nr ]), where the "objects" value is an array of
1084  * objects that match the specified filters, sort order and pagination.
1085  * The other indices contain pagination information ("total" is the total
1086  * number of objects after filtering but before applying "length", "length"
1087  * is the requested number of items to return (as in the parameter list),
1088  * "start" is the start index of the result in the total number of objects,
1089  * and "page" is the page number (starting with 1) of pages with "length"
1090  * objects each, or 0 if invalid).
1091  */
1092 mapping get_mails_paginated ( object|void mail_folder, array|void filters, array|void sort, int|void offset, int|void length )
1093 {
1094  return get_module( "searching" )->paginate_object_array(
1095  mail_folder->get_annotations(), filters, sort, offset, length );
1096 }
1097 
1098 /**
1099  * Returns the user's emails, optionally filtered, sorted and limited by
1100  * offset and length. This returns the same as the "objects" index in the
1101  * result of get_mails_paginated() and is here for compatibility reasons and
1102  * ease of use (if you don't need pagination information).
1103  *
1104  * @see get_mails_paginated
1105  */
1106 array get_mails_filtered ( object|void mail_folder, array|void filters, array|void sort, int|void offset, int|void length )
1107 {
1108  return get_mails_paginated( mail_folder, filters, sort, offset, length )["objects"];
1109 }
1110 
1111 object get_mailbox()
1112 {
1113 }
1114 
1115 /**
1116  * Get (or create if not existing) the sent mail folder of the user.
1117  *
1118  * @param name optional name for the folder if it is created (default: "sent")
1119  * @return the sent mail folder of the user (if the user has none, it will
1120  * be created and returned)
1121  */
1122 object create_sent_mail_folder ( void|string name ) {
1123 }
1124 
1125 /**
1126  * Get the sent mail folder of the user.
1127  *
1128  * @return the sent mail folder of the user, or 0 if the user has none
1129  */
1130 object get_sent_mail_folder () {
1131  return query_attribute( USER_MAIL_SENT );
1132 }
1133 
1134 /**
1135  * Set a sent mail folder for the user. If the user already has a sent mail
1136  * folder, then it will be turned into a regular mail folder of the user
1137  * and the new folder will be marked as the user's sent mail folder.
1138  *
1139  * @param folder a mail folder to be set as the new sent mail folder of the
1140  * user
1141  * @return the new sent mail folder of the user
1142  */
1143 object set_sent_mail_folder ( object folder ) {
1144  object old = query_attribute( USER_MAIL_SENT );
1145  object res = set_attribute( USER_MAIL_SENT, folder );
1146  if ( objectp(old) &&
1147  old->query_attribute( OBJ_TYPE ) == "container_mailbox_sent" )
1148  old->set_attribute( OBJ_TYPE, "container_mailbox" );
1149  if ( !objectp(res) ) return 0;
1150  res->set_attribute( OBJ_TYPE, "container_mailbox_sent" );
1151  if ( objectp(res) && search( get_annotations(), res ) < 0 )
1152  steam_user_error( "Cannot set as sent-mail folder because the object "
1153  + "is no annotation on the user object." );
1154  return res;
1155 }
1156 
1157 /**
1158  * Query whether the user is storing sent mails in a sent mail folder.
1159  * If the user has no sent mail folder then mails he sends won't be
1160  * stored, independant of this setting.
1161  *
1162  * @see get_sent_mail_folder
1163  *
1164  * @return 1 if the user is storing sent mails, or 0 if not
1165  */
1166 bool is_storing_sent_mail () {
1167  return query_attribute( USER_MAIL_STORE_SENT );
1168 }
1169 
1170 /**
1171  * Set whether the user shall store sent mails in a sent mail folder.
1172  * If the user has no sent mail folder then mails he sends won't be
1173  * stored, independant of this setting.
1174  *
1175  * @see create_sent_mail_folder
1176  * @see set_sent_mail_folder
1177  *
1178  * @param store set to 0 if the user shall not store sent mails, or to 1
1179  * if the user shall store sent mails
1180  * @return 1 if the user is now storing sent mails, or 0 if not
1181  */
1182 bool set_is_storing_sent_mail ( bool store ) {
1183  return set_attribute( USER_MAIL_STORE_SENT, (int) store );
1184 }
1185 
1186 
1187 /**
1188  * Mail the user some message by using steam's internal mail system.
1189  * If the sending user has activated sent mail storage, then a copy of the
1190  * mail will be stored in her sent mail folder.
1191  *
1192  * @param msg the message body (can be a plaintext or html string, a document
1193  * or a mapping)
1194  * @param subject an optional subject
1195  * @param sender an optional sender mail address
1196  * @param mimetype optional mime type of the message body
1197  * @param headers optional headers for the mail
1198  * @return the created mail object or 0.
1199  */
1200 final object
1201 mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype, void|mapping headers)
1202 {
1203  object mail_obj = do_mail( msg, subject, sender, mimetype, headers );
1204  object sending_user = geteuid() || this_user();
1205  if ( objectp(mail_obj) && objectp(sending_user) &&
1206  sending_user->is_storing_sent_mail() &&
1207  objectp(sending_user->get_sent_mail_folder()) ) {
1208  object mail_copy = mail_obj->duplicate();
1209  if ( objectp(mail_copy) ) {
1210  mail_copy->sanction_object( sending_user, SANCTION_ALL );
1211  get_module( "table:read-documents" )->download_document( 0, mail_copy, UNDEFINED ); // mark as read
1212  foreach ( mail_copy->get_annotations(), object ann )
1213  get_module( "table:read-documents" )->download_document( 0, ann, UNDEFINED ); // mark as read
1214  sending_user->get_sent_mail_folder()->add_annotation( mail_copy );
1215  }
1216  }
1217  return mail_obj;
1218 }
1219 
1220 /**
1221  * Don't call this method, it is only here for User->mail() and Group->do_send_mail() !!!
1222  */
1223 final object
1224 do_mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype, void|mapping headers)
1225 {
1226  return 0; // these users don't receive mails
1227 
1228  object factory = _Server->get_factory(CLASS_DOCUMENT);
1229  object user = geteuid() || this_user();
1230 
1231  object message;
1232 
1233  if ( !objectp(user) ) user = _ROOT;
1234  if ( mappingp(subject) )
1235  subject = subject[do_query_attribute(USER_LANGUAGE)] || subject["english"];
1236  if ( objectp(msg) && !stringp(subject) )
1237  subject = msg->query_attribute( OBJ_DESC ) || msg->get_identifier();
1238  if ( !stringp(subject) )
1239  subject = "Message from " + user->get_identifier();
1240  if ( !stringp(mimetype) )
1241  mimetype = "text/html";
1242 
1243  if ( objectp(msg) ) {
1244  message = msg;
1245  // OBJ_DESC is subject of messages
1246  string desc = msg->query_attribute(OBJ_DESC);
1247  if ( !stringp(desc) || desc == "" )
1248  msg->set_attribute(OBJ_DESC, msg->get_identifier());
1249  if ( !stringp(msg->query_attribute("mailto")) )
1250  }
1251  else {
1252  message = factory->execute( ([ "name": replace(subject, "/", "_"),
1253  "mimetype": mimetype,
1254  ]) );
1255  if ( mappingp(msg) )
1256  msg = msg[do_query_attribute(USER_LANGUAGE)] || msg["english"];
1257  message->set_attribute(OBJ_DESC, subject);
1258  if ( lower_case(mimetype) == "text/html" && stringp(msg) ) {
1259  // check whether <html> and <body> tags are missing:
1260  msg = Messaging.fix_html( msg );
1261  }
1262  message->set_content(msg);
1263  }
1264  do_add_annotation(message);
1265  // give message to the user it was send to
1266  if ( objectp(this_user()) )
1267  message->sanction_object(this_user(), 0); // remove permissions of user
1268  message->set_acquire(0); // make sure only the user can read it
1269 
1270  if ( do_query_attribute(USER_FORWARD_MSG) == 1 ) {
1271  string email = do_query_attribute(USER_EMAIL);
1272  if ( stringp(email) && strlen(email) > 0 && search(email, "@") > 0)
1273  {
1274  if ( message->query_attribute(MAIL_MIMEHEADERS) )
1275  get_module("smtp")->send_mail_mime(do_query_attribute(USER_EMAIL), message);
1276  else {
1277  string from = sender;
1278  if ( (!stringp(sender) || search(sender, "@") == -1) ) {
1279  from = Messaging.get_quoted_name( user ) +
1280  "<" + user->get_steam_email() + ">";
1281  }
1282  msgMessage->set_subject( subject );
1283  if ( mappingp(headers) ) {
1284  mapping msgHeaders = message->query_attribute(MAIL_MIMEHEADERS_ADDITIONAL);
1285  if ( !mappingp(msgHeaders) ) msgHeaders = ([ ]);
1286  msgHeaders |= headers;
1287  message->set_attribute( MAIL_MIMEHEADERS_ADDITIONAL, msgHeaders );
1288  }
1289  get_module("forward")->send_message( ({
1290  get_user_name() }), msgMessage );
1291  }
1292  }
1293  }
1294  return message;
1295 }
1296 
1297 /**
1298 private:
1299  * public tell (and private tell) will send a mail to the user
1300  * if there is no chat-socket connected.
1301  *
1302  * @param msg - the msg to tell
1303 private:
1304  * @see private_tell
1305  */
1306 final bool
1307 message(string msg)
1308 {
1309  try_event(EVENT_TELL, geteuid() || this_user(), msg);
1310 
1311  // no steam client connected - so user would not see message
1312 
1313  run_event(EVENT_TELL, geteuid() || this_user(), msg);
1314  return true;
1315 }
1316 
1317 public:
1318 
1319 
1320 /**
1321  * Get the current status of the user object. This goes through all
1322  * connected sockets and checks their features. The result of the function
1323  * are all features of the connected sockets.
1324  *
1325  * @return features of the connected sockets.
1326  */
1327 int get_status(void|int stats)
1328 {
1329  int status = 0;
1330 
1331  foreach ( indices(mSockets), string sid ) {
1332  object socket = mSockets[sid];
1333  if ( objectp(socket) ) {
1334  status |= CLIENT_STATUS_CONNECTED;
1335  status |= socket->get_client_features();
1336  }
1337  else
1338  m_delete(mSockets, sid);
1339  }
1340  foreach ( indices(mVirtualConnections), mixed sid ) {
1341  if ( !stringp(sid) ) continue;
1342  object connection = mVirtualConnections[sid];
1343  if ( objectp(connection) ) {
1344  status |= CLIENT_STATUS_CONNECTED;
1345  status |= connection->get_client_features();
1346  }
1347  else
1348  m_delete( mVirtualConnections, sid );
1349  }
1350  if ( zero_type(stats) )
1351  return status;
1352  return status & stats;
1353 }
1354 
1355 /**
1356  * check if a socket with some connection class exists
1357  *
1358  * @param clientClass - the client class to check
1359  * @return if a socket with the client class is present
1360  */
1361 bool connected(string clientClass)
1362 {
1363  foreach ( values(mSockets), object socket ) {
1364  if ( objectp(socket) ) {
1365  if ( socket->get_client_class() == clientClass )
1366  return true;
1367  }
1368  }
1369  foreach ( values(mVirtualConnections), object connection ) {
1370  if ( objectp(connection) ) {
1371  if ( connection->get_client_class() == clientClass )
1372  return true;
1373  }
1374  }
1375  return false;
1376 }
1377 
1378 /**
1379  * Set the active group - can only be called by a socket of the user
1380  *
1381  * @param object grp - the group to be activated.
1382  * @see get_active_group
1383  */
1384 void set_active_group(object grp)
1385 {
1386  if ( search(aoGroups, grp) == -1 )
1387  THROW("Trying to activate a group the user is not member of !",
1388  E_ACCESS);
1389 
1390  oActiveGrp = grp;
1391  require_save(STORE_USER);
1392 }
1393 
1394 /**
1395  * Returns the currently active group of the user
1396  *
1397  * @return The active group or the steam-user group.
1398  * @see set_active_group
1399  */
1400 object get_active_group()
1401 {
1402  if ( !objectp(oActiveGrp) )
1403  return _STEAMUSER;
1404  return oActiveGrp;
1405 }
1406 
1407 /**
1408  * Called when a command is done. Only sockets can call this function.
1409  *
1410  * @param t - time of the command
1411  * @see get_idle
1412  */
1413 void command_done(int t)
1414 {
1415  iCommandTime = t;
1416 }
1417 
1418 /**
1419  * Get the idle time of the user.
1420  *
1421  * @return the time the user has not send a command
1422  * @see command_done
1423  */
1424 int get_idle()
1425 {
1426  return time() - iCommandTime;
1427 }
1428 
1429 /**
1430  * Check if it is possible to insert a given object in the user container.
1431  *
1432  * @param object obj - the object to insert.
1433  * @return true
1434  */
1435 protected:
1436  bool check_insert(object obj)
1437 {
1438  return true;
1439 }
1440 
1441 public:
1442 
1443 
1444 void add_trail(object visit, int max_size)
1445 {
1446  array aTrail = do_query_attribute("trail");
1447  if ( !arrayp(aTrail) )
1448  aTrail = ({ visit });
1449  else {
1450  if ( visit == aTrail[-1] )
1451  return;
1452  aTrail += ({ visit });
1453  if ( sizeof(aTrail) > max_size )
1454  aTrail = aTrail[sizeof(aTrail)-max_size..];
1455  }
1456  set_attribute("trail", aTrail);
1457 }
1458 
1459 array get_trail()
1460 {
1461  return do_query_attribute("trail");
1462 }
1463 
1464 object get_last_trail()
1465 {
1466  array rooms = do_query_attribute("trail");
1467  if ( arrayp(rooms) )
1468  return rooms[-1];
1469  return 0;
1470 }
1471 
1472 array get_attribute_readers(string key)
1473 {
1474  return mAttributeAccess[key];
1475 }
1476 
1477 void add_attribute_reader(string key, object group)
1478 {
1479  if ( !arrayp(mAttributeAccess[key]) ) {
1480  mAttributeAccess[key] = ({ group });
1481  }
1482  else {
1483  mAttributeAccess[key] += ({ group });
1484  }
1485  require_save(STORE_USER, "AttributeAccess");
1486 }
1487 
1488 void remove_attribute_reader(string key, object group)
1489 {
1490  if ( arrayp(mAttributeAccess[key]) ) {
1491  mAttributeAccess[key] -= ({ group });
1492  require_save(STORE_USER, "AttributeAccess");
1493  }
1494 }
1495 
1496 
1497 void check_read_attribute(string key, object user)
1498 {
1499  if (!objectp(user) || user==_ROOT)
1500  return;
1501  if ( mAttributeAccess[key] ) {
1502  return;
1503 
1504  array readers = mAttributeAccess[key];
1505  if ( arrayp(readers) && sizeof(readers) > 0 ) {
1506  if ( _ADMIN->is_member(user) )
1507  return;
1508  // check access for this_user(), because of restricted attributes
1509  foreach(readers, object reader) {
1510  if ( reader == _WORLDUSER || reader == user )
1511  return;
1512  if ( reader->get_object_class() & CLASS_GROUP )
1513  if ( reader->is_virtual_member(user) )
1514  return;
1515  }
1516  THROW(sprintf("Access Denied for %s to read Attribute %s",
1517  user->get_user_name(),
1518  key),
1519  E_ACCESS);
1520  }
1521  }
1522 }
1523 
1524 mixed query_attribute(string key)
1525 {
1526  check_read_attribute(key, geteuid() || this_user());
1527  return ::query_attribute(key);
1528 }
1529 
1530 
1531 protected:
1532  bool do_set_attribute(string key, mixed|void val)
1533 {
1534  mixed res = ::do_set_attribute(key, val);
1535  if ( key == USER_ID ||
1536  key == USER_FIRSTNAME ||
1537  key == USER_FULLNAME ||
1538  key == USER_EMAIL )
1539  return res;
1540 }
1541 
1542 public:
1543 
1544 mixed move(object to)
1545 {
1546  add_trail(to, 20);
1547  return ::move(to);
1548 }
1549 
1550 void confirm_contact()
1551 {
1552  mapping confirmed = do_query_attribute(USER_CONTACTS_CONFIRMED) || ([ ]);
1553  confirmed[this_user()] = 1;
1554  do_set_attribute(USER_CONTACTS_CONFIRMED, confirmed);
1555 }
1556 
1557 int __get_command_time() { return iCommandTime; }
1558 int get_object_class() { return ::get_object_class() | CLASS_USER; }
1559 final bool is_user() { return true; }
1560 
1561 /**
1562  * Get a list of sockets of this user.
1563  *
1564  * @return the list of sockets of the user
1565  */
1566 array get_sockets()
1567 {
1568  return values(mSockets);
1569 }
1570 
1571 /**
1572  * Get a list of sockets of this user.
1573  *
1574  * @return the list of sockets of the user
1575  */
1576 array get_virtual_connections () {
1577  return values(mVirtualConnections);
1578 }
1579 
1580 string get_ip(string|int sname)
1581 {
1582  foreach(values(mSockets), object sock) {
1583  if (!objectp(sock))
1584  continue;
1585  if ( stringp(sname) && sock->get_socket_name() == sname )
1586  return sock->get_ip();
1587  else if (sock->get_client_features() & sname )
1588  return sock->get_ip();
1589  }
1590  foreach ( values(mVirtualConnections), mixed conn ) {
1591  if ( !objectp(conn) ) continue;
1592  if ( stringp(sname) && conn->get_connection_name() == sname )
1593  return conn->get_ip();
1594  else if ( conn->get_client_features() & sname )
1595  return conn->get_ip();
1596  }
1597  return "0.0.0.0";
1598 }
1599 
1600 string describe()
1601 {
1602  return "~"+sUserName+"(#"+get_object_id()+","+get_status()+","+get_ip(1)+
1603  ")";
1604 }
1605 
1606 
1607 
1608 
1609 };