1 /* Copyright (C) 2000-2006  Thomas Bopp, Thorsten Hampel, Ludger Merkens
     3  *  This program is free software; you can redistribute it and/or modify
     4  *  it under the terms of the GNU General Public License as published by
     5  *  the Free Software Foundation; either version 2 of the License, or
     6  *  (at your option) any later version.
     8  *  This program is distributed in the hope that it will be useful,
     9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  *  GNU General Public License for more details.
    13  *  You should have received a copy of the GNU General Public License
    14  *  along with this program; if not, write to the Free Software
    15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    17  * $Id: User.pike,v 1.7 2010/01/25 19:18:18 astra Exp $
    19 inherit "/classes/Container" : __cont;
    20 inherit "/base/member" :     __member;
    21 #include <attributes.h>
    32 #include <exception.h>
    33 //! this is the user object. It keeps track of connections and membership
    35 class User : public Container,member{
    44 //#define EVENT_USER_DEBUG
    46 #ifdef EVENT_USER_DEBUG
    47 #define DEBUG_EVENT(s, args...) werror(s+"\n", args)
    49 #define DEBUG_EVENT(s, args...)
    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
    59 private  mapping mAttributeAccess = ([ ]); // set readable
    61 private  string         sTicket;
    62 private  array aTickets;
    63 private  int        iActiveCode;
    67 private  mapping     mSocketEvents;
    68          mapping mVirtualConnections;
    70 bool userLoaded = false;
    72  Thread.Mutex annotationMutex = Thread.Mutex();
    74 bool   check_swap() { return false; }
    75 bool   check_upgrade() { return false; }
    84     mSocketEvents = ([ ]);
    85     mVirtualConnections = ([ ]);
    88     /* the user name is a locked attribute */
    89     add_data_storage(STORE_USER, store_user_data, restore_user_data, 1);
    95  * Constructor of the user object.
   110     mAttributeAccess = ([ ]);
   117  * Creating a duplicate of the user wont work.
   119  * @return throws an error
   121 object duplicate(void|mapping vars)
   123     THROW("User cannot be duplicated !\n", E_ERROR);
   127  * register the object in the database.
   129  * @param name - the name of the object
   132  void database_registration(string name)
   139  * Destructor of the user.
   149    THROW("Cannot delete the root user !", E_ACCESS);
   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) {
   165       object workroom = do_query_attribute(USER_WORKROOM);
   166       if ( objectp(workroom) ) workroom->delete();
   169       FATAL( "Failed to delete workroom of \"%s\": %O\n%O\n", sUserName, err[0], err[1] );
   171       object bookmarks = do_query_attribute(USER_BOOKMARKROOM);
   172       if ( objectp(bookmarks) ) bookmarks->delete();
   175       FATAL( "Failed to delete bookmars of \"%s\": %O\n%O\n", sUserName, err[0], err[1] );
   177       object calendar = do_query_attribute(USER_CALENDAR);
   178       if ( objectp(calendar) ) calendar->delete();
   181       FATAL( "Failed to delete calendar of \"%s\": %O\n%O\n", sUserName, err[0], err[1] );
   183     __member::delete_object();
   184     __cont::delete_object();
   190  * Dont update a users name.
   192 void update_identifier()
   197  * Dont update a users path (its ~username anyway)
   204  * Create all the exits to the groups the user is member of.
   207 void create_group_exits()
   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 = ([ ]);
   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(), ]);
   224    foreach ( indices(mExits), object exit ) {
   227        if ( !objectp(exit) ) 
   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) )
   235                if ( exit_to->get_object_id() == exit->get_object_id() )
   240            object factory = _Server->get_factory(CLASS_EXIT);
   241            object exit = factory->execute(
   242                ([ "name": mExits[exit], "exit_to": exit, ]) );
   243            exit->move(workroom);
   250  string new_session_id()
   253 #if constant(Crypto.Random) 
   254     sid = sprintf("%x", hash(Crypto.Random.random_string(10)));
   256     sid = sprintf("%x", hash(random(1000000) + time() + sUserName+sUserPass));
   264  * Connect the user object to a steamsocket.
   266  * @param obj - the steamsocket to connect to
   267  * @return the time of the last login
   276     LOG("Connecting "+ get_identifier()+" with "+ obj->describe()+"\n");
   278     if ( !IS_SOCKET(CALLER) )
   279    THROW("Trying to connect user to non-steamsocket !", E_ACCESS);
   281     array aoSocket = values(mSockets);
   282     for ( i = sizeof(aoSocket) - 1; i >= 0; i-- ) {
   283    if ( aoSocket[i] == obj )
   286     int features = obj->get_client_features();
   287     int prev_features = get_status();
   290     string sid = new_session_id();
   291     while ( objectp(mSockets[sid]) || objectp(mVirtualConnections[sid]) )
   292    sid = new_session_id();
   296     m_delete(mSockets, 0);
   297     foreach ( indices(mSockets), sid) 
   298    if ( !objectp(mSockets[sid]) && !stringp(mSockets[sid]) ) 
   299        m_delete(mSockets, sid);
   301     last_login = do_query_attribute(USER_LAST_LOGIN);
   302     do_set_attribute(USER_LAST_LOGIN, time());
   304     if ( (prev_features & features) != features ) 
   310  * Connect the user object to a virtual connection.
   312  * @param obj - the virtual connection to connect to
   313  * @return the time of the last login
   314  * @see disconnect_virtual
   316 int connect_virtual ( object connection ) {
   318   LOG( "Connecting (virtual) " + get_identifier() + " with "
   319        + connection->describe() + "\n" );
   320   if ( has_value( mVirtualConnections, connection ) )
   322   int features = connection->get_client_features();
   323   int prev_features = get_status();
   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;
   331   m_delete( mVirtualConnections, 0 );
   332   foreach ( indices(mVirtualConnections), sid )
   333     if ( !objectp(mVirtualConnections[sid]) &&
   334          !stringp(mVirtualConnections[sid]) )
   335       m_delete( mVirtualConnections, sid );
   337   last_login = do_query_attribute( USER_LAST_LOGIN );
   338   do_set_attribute( USER_LAST_LOGIN, time() );
   340   if ( (prev_features & features) != features ) 
   345 string get_session_id() 
   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 )
   356 string get_virtual_session_id () {
   357   mixed sid = mVirtualConnections[ CALLER ];
   358   if ( stringp(sid) ) return sid;
   362 bool join_group(object grp)
   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);
   371 bool leave_group(object grp)
   373   try_event(EVENT_USER_LEAVE_GROUP, CALLER, grp);
   374   mixed res = ::leave_group(grp);
   375   run_event(EVENT_USER_LEAVE_GROUP, CALLER, grp);
   380  * Close the connection to socket and logout.
   382  * @param obj - the object to remove from active socket list
   387 close_connection(object obj)
   389     if ( which_socket(obj) < 0 ) return;
   391     try_event(EVENT_LOGOUT, CALLER, obj);
   393     foreach(indices(mSockets), string sid)
   394    if ( mSockets[sid] == obj )
   395        m_delete(mSockets, sid);
   397     int cfeatures = obj->get_client_features();
   398     int features = get_status();
   400     if ( (cfeatures & features) != cfeatures ) 
   402     ASSERTINFO(which_socket(obj) < 0, "Still connected to socket !");
   403     DEBUG_EVENT(sUserName+": logout event....");
   404     run_event(EVENT_LOGOUT, CALLER, obj);
   410  * Close the connection to a virtual connection and logout.
   412  * @param obj - the object to remove from active virtual connection list
   413  * @see disconnect_virtual
   416  void close_virtual_connection ( object connection ) {
   417   if ( !has_value( mVirtualConnections, connection ) ) return;
   419   try_event( EVENT_LOGOUT, CALLER, connection );
   421   m_delete( mVirtualConnections, mVirtualConnections[connection] );
   422   m_delete( mVirtualConnections, connection );
   424   int cfeatures = connection->get_client_features();
   425   int features = get_status();
   427   if ( (cfeatures & features) != cfeatures ) 
   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 );
   438  * Disconnect the CALLER socket from this user object.
   444     object socket = CALLER;
   447     if ( which_socket(socket) == -1 )
   450     if ( arrayp(mSocketEvents[socket]) ) {
   451    foreach ( mSocketEvents[socket], mixed event_data )
   452        if ( arrayp(event_data) )
   453            remove_event(@event_data);
   455     // get the remaining status of the user
   457     array aoSocket = values(mSockets);
   458     foreach ( aoSocket, mixed sock ) {
   459    if ( objectp(sock) && sock != socket ) {
   460        status |= sock->get_client_features();
   463     foreach ( values(mVirtualConnections), object conn ) {
   465         status |= conn->get_client_features();
   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) ) 
   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) )
   482     close_connection(socket);
   486  * Disconnect the CALLER virtual connection from this user object.
   488  * @see connect_virtual
   490 void disconnect_virtual () {
   491   object connection = CALLER;
   493   if ( !has_value( mVirtualConnections, connection) )
   496   // get the remaining status of the user
   498   foreach ( values(mSockets), object sock ) {
   500       status |= sock->get_client_features();
   502   foreach ( values(mVirtualConnections), object conn ) {
   503     if ( objectp(conn) && conn != connection )
   504       status |= conn->get_client_features();
   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) )
   520   close_virtual_connection( connection );
   524  * find out if the object is one of the connected sockets
   526  * @param obj - the object to find out about
   527  * @return the position of the socket in the socket array
   533 which_socket(object obj)
   535     return search(values(mSockets), obj);
   541  * Activate the login. Successfull activation code is required to do so!
   543  * @param int activation - the activation code
   544  * @return true or false
   546 bool activate_user(int|void activation)
   548     if ( activation == iActiveCode || _ADMIN->is_member(this_user()) ) {
   550    require_save(STORE_USER);
   557  * Set the activation code for an user - this is done by the factory.
   559  * @param int activation - the activation code.
   562 void set_activation(int activation)
   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);
   572  * Find out if the user is inactivated.
   574  * @return activation code set or not.
   576 bool get_activation()
   578     return iActiveCode != 0;
   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.
   587  * @param pw - the password to check
   588  * @param uid - the user object
   589  * @return if the password matches or not
   591 bool check_user_password(string pw)
   593     if ( !stringp(sUserPass) ) {
   594       if ( get_module("auth")->allow_zero_passwords() )
   602    MESSAGE("Trying to authenticate with inactivated user !");
   603    return false; // as long as the login is not activated
   606     if ( stringp(sTicket) ) 
   608    if ( verify_crypt_md5(pw, sTicket) ) {
   609        sTicket = 0; // ticket used
   613     if ( arrayp(aTickets) && sizeof(aTickets) > 0 ) {
   614    array tickets = copy_value(aTickets);
   615    foreach(tickets, string ticket) {
   617        sscanf(ticket, "%*s_%d", t);
   619            aTickets -= ({ ticket });
   620            require_save(STORE_USER);
   622        else if ( pw == ticket )
   626     // allow login with any session ID from a connected socket
   627     foreach ( indices(mSockets), string sid)
   631     if ( !stringp(sUserPass) && !get_module("auth")->allow_zero_passwords() )
   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) );
   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);
   645     if ( strlen(sUserPass) < 3 || sUserPass[0..2] != "$1$" ) 
   646       return crypt(pw, sUserPass); // normal crypt check
   648     return verify_crypt_md5(pw, sUserPass);
   652  * Get a ticket from the server - authenticate to the server with
   653  * this ticket once. Optional parameter t gives time the ticket
   656  * @param void|int t - the validity of the ticket
   658  * @see check_user_password
   660 final string get_ticket(void|int t)
   662    THROW("Invalid call to get_ticket() !", E_ACCESS);
   664     try_event(EVENT_USER_NEW_TICKET, CALLER, 0);
   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) ) {
   674      aTickets += ({ ticket });
   676      aTickets = ({ ticket });
   677    run_event(EVENT_USER_NEW_TICKET, CALLER, "********");
   678    require_save(STORE_USER);
   682     sTicket = make_crypt_md5(ticket);
   683     run_event(EVENT_USER_NEW_TICKET, CALLER, "*********");
   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
   695 string get_old_password()
   697         THROW(sprintf("%O is not permitted to read the old password!", CALLER),
   699     //werror("get_old_password: %O\n", this_user());
   704  * Set the user password and save an md5 hash of it.
   706  * @param pw - the new password for the user
   707  * @return if successfull
   708  * @see check_user_pasword
   711 set_user_password(string pw, int|void crypted, string|void oldpw)
   714     try_event(EVENT_USER_CHANGE_PW, CALLER);
   718       sUserPass = make_crypt_md5(pw);
   719     require_save(STORE_USER);
   720     run_event(EVENT_USER_CHANGE_PW, CALLER);
   726 set_user_password_plain(string pw, int|void crypted)
   728     try_event(EVENT_USER_CHANGE_PW, CALLER);
   732       sPlainPass = make_crypt_md5(pw);
   733     require_save(STORE_USER);
   734     run_event(EVENT_USER_CHANGE_PW, CALLER);
   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.
   743  * @return the users password.
   746 get_user_password(string|void pw)
   748     // security problem ? ask for read permissions at least - 
   749     // probably for admin?
   750     return copy_value(sUserPass);
   754  * Get the user object of the user which is this object.
   757 object get_user_object()
   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 )
   765  * @return the e-mail adress of this user
   767 string get_steam_email()
   769     return sUserName  + "@" + _Server->get_server_name();
   773  * set the user name, which is only allowed for the factory.
   775  * @param string name - the new name of the user.
   778 set_user_name(string name)
   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!");
   785     string old_name = sUserName;
   788     do_set_attribute(OBJ_NAME, name);
   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" );
   795             workroom->update_path();
   798     require_save(STORE_USER);
   804   return copy_value(sUserName);
   808  * Get the complete name of the user, that is first and lastname.
   809  * Last name attribute is called FULLNAME because of backwards compatibility.
   811  * @return the first and last name
   816   lname = do_query_attribute(USER_LASTNAME);
   817   fname = do_query_attribute(USER_FIRSTNAME);
   818   if ( !stringp(fname) )
   821   return fname + " " + lname;
   826  * restore the use specific data
   828  * @param data - the unserialized data of the user
   830  * @see store_user_data
   834 restore_user_data(mixed data, string|void index)
   836     if ( CALLER != _Database ) 
   837       THROW("Invalid call to restore_user_data()", E_ACCESS);
   839     if ( equal(data, ([ ])) ) {
   840       FATAL("Empty load in restore_user_data()");
   843     if ( userLoaded && !stringp(index) ) 
   844       steam_error("Loading already loaded user: " + sUserName + ":"+
   846     if (zero_type(index)) // no index set restore all 
   848    if ( !stringp(data->UserName) ) {
   849        FATAL("In: " + get_object_id() + ": "+ 
   850              "Cannot restore user with 0-name, already got " +
   855         sUserName    = data["UserName"];
   856         sUserPass    = data["UserPassword"];
   857         sPlainPass   = data["PlainPass"];
   858         sTicket      = data["UserTicket"];
   859         if ( !stringp(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) )
   868    if (!mappingp(mAttributeAccess))
   869      mAttributeAccess = ([ ]);
   876          if ( !stringp(data) ) {
   877              FATAL("In: " + get_object_id() + 
   878                    " : Cannot restore user with null, previous name " +
   884           case "UserPassword" : sUserPass = data; 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;
   897               if (arrayp(aTickets))
   904     //ASSERTINFO(arrayp(aoGroups),"Group is not an array !");
   905     if ( !arrayp(aoGroups) )
   912  * returns the userdata that will be stored in the Database
   914  * @return array containing user data
   916  * @see restore_user_data
   920 store_user_data(string|void index)
   922     if ( CALLER != _Database )
   923       THROW("Invalid call to store_user_data()", E_ACCESS);
   925     if (zero_type(index))
   928             "UserName":sUserName,
   929             "UserPassword":sUserPass, 
   930             "PlainPass":sPlainPass,
   932             "Activation": iActiveCode,
   934             "ActiveGroup": oActiveGrp,
   935             "UserTicket" : sTicket,
   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;
   949      steam_error("Invalid index in store_user_data(%O)\n", index);
   957  * the event listener function. The event is automatically send
   960  * @param event - the type of event
   961  * @param args - the different args for each event
   965 final int notify_event(int event, mixed ... args)
   970     DEBUG_EVENT(sUserName+":notify_event("+event+",....)");
   971     sockets = values(mSockets);
   973     if ( !arrayp(sockets) || sizeof(sockets) == 0 )
   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]);
   983        if ( sockets[i]->get_client_features() & CLIENT_FEATURES_EVENTS ){
   984                 LOG("Notifying socket " + i + " about event: " + event);
   985            sockets[i]->notify(event, @args);
   993  bool do_add_annotation(object mail)
   997   object lock = annotationMutex->lock();
   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);
  1005     result = ::do_add_annotation(mail);
  1018  * Get the annotations, eg e-mails of the user.
  1020  * @return list of annotations
  1022 array get_annotations()
  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);
  1031    do_set_attribute(USER_MAILBOX, 0);
  1033     return ::get_annotations();
  1037  * Get the mails of a user.
  1039  * @return array of objects of mail documents
  1041 array get_mails(void|int from_obj, void|int to_obj)
  1043   array mails = get_annotations();
  1044   if ( sizeof(mails) == 0 )
  1047   if ( !intp(to_obj) )
  1048     to_obj = sizeof(mails);
  1049   if ( !intp(from_obj) )
  1051   return mails[from_obj-1..to_obj-1];
  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.
  1062  * Return the 10 newest mails whose subjects do not start with "{SPAM}",
  1064  * get_mails_filtered(
  1066  *     ({ "-", "attribute", "OBJ_DESC", "prefix", "{SPAM}" }),
  1067  *     ({ "+", "class", CLASS_DOCUMENT }),
  1070  *     ({ ">", "attribute", "OBJ_CREATION_TIME" })
  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)
  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).
  1092 mapping get_mails_paginated ( object|void mail_folder, array|void filters, array|void sort, int|void offset, int|void length )
  1094   return get_module( "searching" )->paginate_object_array(
  1095       mail_folder->get_annotations(), filters, sort, offset, length );
  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).
  1104  * @see get_mails_paginated
  1106 array get_mails_filtered ( object|void mail_folder, array|void filters, array|void sort, int|void offset, int|void length )
  1108   return get_mails_paginated( mail_folder, filters, sort, offset, length )["objects"];
  1111 object get_mailbox()
  1116  * Get (or create if not existing) the sent mail folder of the user.
  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)
  1122 object create_sent_mail_folder ( void|string name ) {
  1126  * Get the sent mail folder of the user.
  1128  * @return the sent mail folder of the user, or 0 if the user has none
  1130 object get_sent_mail_folder () {
  1131   return query_attribute( USER_MAIL_SENT );
  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.
  1139  * @param folder a mail folder to be set as the new sent mail folder of the
  1141  * @return the new sent mail folder of the user
  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." );
  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.
  1162  * @see get_sent_mail_folder
  1164  * @return 1 if the user is storing sent mails, or 0 if not
  1166 bool is_storing_sent_mail () {
  1167   return query_attribute( USER_MAIL_STORE_SENT );
  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.
  1175  * @see create_sent_mail_folder
  1176  * @see set_sent_mail_folder
  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
  1182 bool set_is_storing_sent_mail ( bool store ) {
  1183   return set_attribute( USER_MAIL_STORE_SENT, (int) store );
  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.
  1192  * @param msg the message body (can be a plaintext or html string, a document
  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.
  1201 mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype, void|mapping headers)
  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 );
  1221  * Don't call this method, it is only here for User->mail() and Group->do_send_mail() !!!
  1224 do_mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype, void|mapping headers)
  1226     return 0;  // these users don't receive mails
  1228     object factory = _Server->get_factory(CLASS_DOCUMENT);
  1229     object user = geteuid() || this_user();
  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";
  1243     if ( objectp(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")) )
  1252       message = factory->execute( ([ "name": replace(subject, "/", "_"),
  1253                                 "mimetype": mimetype, 
  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 );
  1262       message->set_content(msg);
  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
  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)
  1274      if ( message->query_attribute(MAIL_MIMEHEADERS) )
  1275        get_module("smtp")->send_mail_mime(do_query_attribute(USER_EMAIL), message);
  1277             string from = sender;
  1278             if ( (!stringp(sender) || search(sender, "@") == -1) ) {
  1279          from = Messaging.get_quoted_name( user ) +
  1280                 "<" + user->get_steam_email() + ">";
  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 );
  1289             get_module("forward")->send_message( ({ 
  1290          get_user_name() }), msgMessage );
  1299  * public tell (and private tell) will send a mail to the user
  1300  * if there is no chat-socket connected.
  1302  * @param msg - the msg to tell
  1309     try_event(EVENT_TELL, geteuid() || this_user(), msg);
  1311     // no steam client connected - so user would not see message
  1313     run_event(EVENT_TELL, geteuid() || this_user(), msg);
  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.
  1325  * @return features of the connected sockets.
  1327 int get_status(void|int stats)
  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();
  1338        m_delete(mSockets, sid);
  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();
  1348         m_delete( mVirtualConnections, sid );
  1350     if ( zero_type(stats) )
  1352     return status & stats;
  1356  * check if a socket with some connection class exists
  1358  * @param clientClass - the client class to check
  1359  * @return if a socket with the client class is present
  1361 bool connected(string clientClass) 
  1363     foreach ( values(mSockets), object socket ) {
  1364    if ( objectp(socket) ) {
  1365        if ( socket->get_client_class() == clientClass )
  1369     foreach ( values(mVirtualConnections), object connection ) {
  1370       if ( objectp(connection) ) {
  1371         if ( connection->get_client_class() == clientClass )
  1379  * Set the active group - can only be called by a socket of the user
  1381  * @param object grp - the group to be activated.
  1382  * @see get_active_group
  1384 void set_active_group(object grp) 
  1386     if ( search(aoGroups, grp) == -1 ) 
  1387    THROW("Trying to activate a group the user is not member of !",
  1391     require_save(STORE_USER);
  1395  * Returns the currently active group of the user
  1397  * @return The active group or the steam-user group.
  1398  * @see set_active_group
  1400 object get_active_group()
  1402     if ( !objectp(oActiveGrp) )
  1408  * Called when a command is done. Only sockets can call this function.
  1410  * @param t - time of the command
  1413 void command_done(int t)
  1419  * Get the idle time of the user.
  1421  * @return the time the user has not send a command
  1426     return time() - iCommandTime;
  1430  * Check if it is possible to insert a given object in the user container.
  1432  * @param object obj - the object to insert.
  1436  bool check_insert(object obj)
  1444 void add_trail(object visit, int max_size)
  1446     array aTrail = do_query_attribute("trail");
  1447     if ( !arrayp(aTrail) ) 
  1448    aTrail = ({ visit });
  1450    if ( visit == aTrail[-1] )
  1452    aTrail += ({ visit });
  1453    if ( sizeof(aTrail) > max_size )
  1454        aTrail = aTrail[sizeof(aTrail)-max_size..];
  1456     set_attribute("trail", aTrail);
  1461     return do_query_attribute("trail");
  1464 object get_last_trail()
  1466     array rooms =  do_query_attribute("trail");
  1467     if ( arrayp(rooms) )
  1472 array get_attribute_readers(string key) 
  1474   return mAttributeAccess[key];
  1477 void add_attribute_reader(string key, object group)
  1479   if ( !arrayp(mAttributeAccess[key]) ) {
  1480     mAttributeAccess[key] = ({ group });
  1483     mAttributeAccess[key] += ({ group });
  1485   require_save(STORE_USER, "AttributeAccess");
  1488 void remove_attribute_reader(string key, object group)
  1490   if ( arrayp(mAttributeAccess[key]) ) {
  1491     mAttributeAccess[key] -= ({ group });
  1492     require_save(STORE_USER, "AttributeAccess");
  1497 void check_read_attribute(string key, object user)
  1499   if (!objectp(user) || user==_ROOT)
  1501   if ( mAttributeAccess[key] ) {
  1504     array readers = mAttributeAccess[key];
  1505     if ( arrayp(readers) && sizeof(readers) > 0 ) {
  1506       if ( _ADMIN->is_member(user) )
  1508       // check access for this_user(), because of restricted attributes  
  1509        foreach(readers, object reader) {
  1510     if ( reader == _WORLDUSER || reader == user )
  1512     if ( reader->get_object_class() & CLASS_GROUP )
  1513       if ( reader->is_virtual_member(user) )
  1516        THROW(sprintf("Access Denied for %s to read Attribute %s", 
  1517                 user->get_user_name(), 
  1524 mixed query_attribute(string key)
  1526   check_read_attribute(key, geteuid() || this_user());
  1527   return ::query_attribute(key);
  1532  bool do_set_attribute(string key, mixed|void val) 
  1534   mixed res = ::do_set_attribute(key, val);
  1535   if ( key == USER_ID || 
  1536        key == USER_FIRSTNAME || 
  1537        key == USER_FULLNAME ||
  1544 mixed move(object to)
  1550 void confirm_contact()
  1552   mapping confirmed = do_query_attribute(USER_CONTACTS_CONFIRMED) || ([ ]);
  1553   confirmed[this_user()] = 1;
  1554   do_set_attribute(USER_CONTACTS_CONFIRMED, confirmed);
  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; }
  1562  * Get a list of sockets of this user.
  1564  * @return the list of sockets of the user
  1568     return values(mSockets);
  1572  * Get a list of sockets of this user.
  1574  * @return the list of sockets of the user
  1576 array get_virtual_connections () {
  1577     return values(mVirtualConnections);
  1580 string get_ip(string|int sname) 
  1582     foreach(values(mSockets), object sock) {
  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();
  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();
  1602     return "~"+sUserName+"(#"+get_object_id()+","+get_status()+","+get_ip(1)+