rob@76: /* rob@76: OSCgroups -- open sound control groupcasting infrastructure rob@76: Copyright (C) 2005 Ross Bencina rob@76: rob@76: This program is free software; you can redistribute it and/or rob@76: modify it under the terms of the GNU General Public License rob@76: as published by the Free Software Foundation; either version 2 rob@76: of the License, or (at your option) any later version. rob@76: rob@76: This program is distributed in the hope that it will be useful, rob@76: but WITHOUT ANY WARRANTY; without even the implied warranty of rob@76: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the rob@76: GNU General Public License for more details. rob@76: rob@76: You should have received a copy of the GNU General Public License rob@76: along with this program; if not, write to the Free Software rob@76: Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. rob@76: */ rob@76: rob@76: #include "GroupServer.h" rob@76: rob@76: #include rob@76: #include rob@76: #include rob@76: #include rob@76: #include rob@76: #include rob@76: #include rob@76: #include rob@76: rob@76: #include "ip/NetworkingUtils.h" rob@76: rob@76: #if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug rob@76: namespace std { rob@76: using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'. rob@76: } rob@76: #endif rob@76: rob@76: /* rob@76: TODO: rob@76: switch to using char * instead of string:: to avoid allocating new strings rob@76: every time we execute a find() rob@76: */ rob@76: rob@76: rob@76: static std::ostream& Log() rob@76: { rob@76: std::time_t t; rob@76: std::time( &t ); rob@76: rob@76: // ctime() returns a constant width 26 char string including trailing \0 rob@76: // the fields are all constant width. rob@76: char s[26]; rob@76: std::strcpy( s, std::ctime( &t ) ); rob@76: s[24] = '\0'; // remove trailing null rob@76: rob@76: std::cout << s << ": "; rob@76: return std::cout; rob@76: } rob@76: rob@76: rob@76: GroupServer::GroupServer( int timeoutSeconds, int maxUsers, int maxGroups ) rob@76: : timeoutSeconds_( timeoutSeconds ) rob@76: , maxUsers_( maxUsers ) rob@76: , maxGroups_( maxGroups ) rob@76: , userCount_( 0 ) rob@76: , groupCount_( 0 ) rob@76: { rob@76: rob@76: } rob@76: rob@76: rob@76: GroupServer::~GroupServer() rob@76: { rob@76: for( const_user_iterator i = users_begin(); i != users_end(); ++i ) rob@76: delete i->second; rob@76: for( const_group_iterator i = groups_begin(); i != groups_end(); ++i ) rob@76: delete i->second; rob@76: } rob@76: rob@76: rob@76: User *GroupServer::CreateUser( const char *userName, const char *userPassword ) rob@76: { rob@76: Log() << "creating user '" << userName << "'." << std::endl; rob@76: rob@76: User *user = new User( userName, userPassword ); rob@76: std::pair r = rob@76: users_.insert( std::make_pair( user->name, user ) ); rob@76: // we assume the caller didn't try to create a user with an existing name rob@76: assert( r.second == true ); rob@76: ++userCount_; rob@76: rob@76: return user; rob@76: } rob@76: rob@76: rob@76: User *GroupServer::FindUser( const char *userName ) rob@76: { rob@76: user_iterator user = users_.find( userName ); rob@76: rob@76: return ( user != users_.end() ) ? user->second : (User*) 0; rob@76: } rob@76: rob@76: rob@76: void GroupServer::PurgeStaleUsers() rob@76: { rob@76: std::time_t currentTime = std::time(0); rob@76: rob@76: for( std::map< std::string, User* >::iterator i = users_.begin(); rob@76: i != users_.end(); /* nothing */ ){ rob@76: rob@76: unsigned long inactiveSeconds = rob@76: i->second->SecondsSinceLastAliveReceived( currentTime ); rob@76: if( inactiveSeconds >= (unsigned int)timeoutSeconds_ ){ rob@76: Log() << "purging user '" << i->second->name << "' after " rob@76: << inactiveSeconds << " seconds of inactivity." << std::endl; rob@76: rob@76: SeparateUserFromAllGroups( i->second ); rob@76: delete i->second; rob@76: --userCount_; rob@76: rob@76: // i think this is safe, we increment i before erasing the rob@76: // element referenced by its old value... rob@76: std::map< std::string, User* >::iterator j = i; rob@76: ++i; rob@76: users_.erase( j ); rob@76: }else{ rob@76: ++i; rob@76: } rob@76: } rob@76: } rob@76: rob@76: rob@76: Group *GroupServer::CreateGroup( rob@76: const char *groupName, const char *groupPassword ) rob@76: { rob@76: Log() << "creating group '" << groupName << "'." << std::endl; rob@76: rob@76: Group *group = new Group( groupName, groupPassword ); rob@76: std::pair r = rob@76: groups_.insert( std::make_pair( group->name, group ) ); rob@76: // we assume the caller didn't try to create an existing group rob@76: assert( r.second == true ); rob@76: ++groupCount_; rob@76: rob@76: return group; rob@76: } rob@76: rob@76: rob@76: Group *GroupServer::FindGroup( const char *groupName ) rob@76: { rob@76: group_iterator group = groups_.find( groupName ); rob@76: rob@76: return ( group != groups_.end() ) ? group->second : (Group*) 0; rob@76: } rob@76: rob@76: rob@76: // The User <-> Group relation is managed as a bidirectional link (each User rob@76: // contains a set of groups which it is associated with, and each Group rob@76: // maintains a set of Users which it is associated with). The Associate* rob@76: // and Separate* methods below create and destroy the bidirectional link rob@76: // RemoveUserReferenceFromGroup is a helper function which only destroys rob@76: // one side of the link. rob@76: rob@76: void GroupServer::AssociateUserWithGroup( User *user, Group* group ) rob@76: { rob@76: Log() << "adding user '" << user->name rob@76: << "' to group '" << group->name << "'." << std::endl; rob@76: rob@76: user->groups_.insert( group ); rob@76: group->users_.insert( user ); rob@76: } rob@76: rob@76: rob@76: void GroupServer::RemoveUserReferenceFromGroup( User *user, Group* group ) rob@76: { rob@76: Log() << "removing user '" << user->name rob@76: << "' from group '" << group->name << "'." << std::endl; rob@76: rob@76: std::set< User* >::iterator i = group->users_.find( user ); rob@76: assert( i != group->users_.end() ); rob@76: rob@76: group->users_.erase( i ); rob@76: rob@76: if( group->users_.empty() ){ rob@76: Log() << "purging empty group '" << group->name << "'." << std::endl; rob@76: rob@76: groups_.erase( group->name ); rob@76: delete group; rob@76: --groupCount_; rob@76: } rob@76: } rob@76: rob@76: rob@76: void GroupServer::SeparateUserFromGroup( User *user, Group* group ) rob@76: { rob@76: user->groups_.erase( group ); rob@76: RemoveUserReferenceFromGroup( user, group ); rob@76: } rob@76: rob@76: rob@76: void GroupServer::SeparateUserFromAllGroups( User *user ) rob@76: { rob@76: for( std::set::iterator i = user->groups_.begin(); rob@76: i != user->groups_.end(); ++i ){ rob@76: rob@76: RemoveUserReferenceFromGroup( user, *i); rob@76: } rob@76: rob@76: user->groups_.clear(); rob@76: } rob@76: rob@76: rob@76: static void UpdateEndpoint( IpEndpointName& dest, const IpEndpointName& src, rob@76: const char *userName, const char *whichEndpoint ) rob@76: { rob@76: if( dest != src ){ rob@76: rob@76: char endpointString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; rob@76: src.AddressAndPortAsString( endpointString ); rob@76: rob@76: Log() << "updating " << whichEndpoint << " endpoint for user '" rob@76: << userName << "' to " << endpointString << "." << std::endl; rob@76: rob@76: dest = src; rob@76: } rob@76: } rob@76: rob@76: rob@76: GroupServer::UserStatus GroupServer::UserAlive( rob@76: const char *userName, const char *userPassword, rob@76: const IpEndpointName& privateEndpoint, rob@76: const IpEndpointName& publicEndpoint, rob@76: const char **groupNamesAndPasswords, rob@76: UserStatus *userGroupsStatus, int groupCount ) rob@76: { rob@76: // find or create the user, aborting if the password for an existing rob@76: // user is incorrect, or if the maximum number of users has been reached rob@76: rob@76: User *user = FindUser( userName ); rob@76: if( user ){ rob@76: if( user->password.compare( userPassword ) != 0 ){ rob@76: Log() << "attempt to update user '" rob@76: << userName << "' with incorrect password." << std::endl; rob@76: return USER_STATUS_WRONG_PASSWORD; rob@76: } rob@76: }else{ rob@76: if( userCount_ == maxUsers_ ){ rob@76: Log() << "user limit reached, user '" rob@76: << userName << "' not admitted." << std::endl; rob@76: return USER_STATUS_SERVER_LIMIT_REACHED; rob@76: }else{ rob@76: user = CreateUser( userName, userPassword ); rob@76: } rob@76: } rob@76: rob@76: UpdateEndpoint( rob@76: user->privateEndpoint, privateEndpoint, userName, "private" ); rob@76: UpdateEndpoint( user->publicEndpoint, publicEndpoint, userName, "public" ); rob@76: rob@76: user->lastAliveMessageArrivalTime = time(0); rob@76: rob@76: rob@76: // previousButNotCurrentGroups begins containing all the groups the user rob@76: // was in before the current message was received. We remove those which rob@76: // the user is still a member of, leaving the set that the user is no rob@76: // longer a member of. We then use the set to remove associations with rob@76: // non-current groups. rob@76: rob@76: std::set previousButNotCurrentGroups( user->groups_ ); rob@76: rob@76: for( int i=0; i < groupCount; ++i ){ rob@76: const char *groupName = groupNamesAndPasswords[i*2]; rob@76: const char *groupPassword = groupNamesAndPasswords[i*2 + 1]; rob@76: rob@76: Group *group = FindGroup( groupName ); rob@76: if( group ){ rob@76: if( user->IsMemberOf( group ) ){ rob@76: // check that previousButNotCurrentGroups contains group before rob@76: // removing it. it won't if we were passed the same group rob@76: // multiple times rob@76: std::set::iterator j = rob@76: previousButNotCurrentGroups.find( group ); rob@76: if( j != previousButNotCurrentGroups.end() ) rob@76: previousButNotCurrentGroups.erase( j ); rob@76: rob@76: userGroupsStatus[i] = USER_STATUS_OK; rob@76: }else{ rob@76: // user isn't in group so join it rob@76: if( group->password.compare( groupPassword ) == 0 ){ rob@76: AssociateUserWithGroup( user, group ); rob@76: userGroupsStatus[i] = USER_STATUS_OK; rob@76: }else{ rob@76: userGroupsStatus[i] = USER_STATUS_WRONG_PASSWORD; rob@76: } rob@76: } rob@76: }else{ // group doesn't exist rob@76: if( groupCount_ == maxGroups_ ){ rob@76: Log() << "group limit reached, group '" rob@76: << groupName << "' not created." << std::endl; rob@76: userGroupsStatus[i] = USER_STATUS_SERVER_LIMIT_REACHED; rob@76: }else{ rob@76: group = CreateGroup( groupName, groupPassword ); rob@76: AssociateUserWithGroup( user, group ); rob@76: userGroupsStatus[i] = USER_STATUS_OK; rob@76: } rob@76: } rob@76: } rob@76: rob@76: for( std::set::iterator j = previousButNotCurrentGroups.begin(); rob@76: j != previousButNotCurrentGroups.end(); ++j ){ rob@76: rob@76: SeparateUserFromGroup( user, *j ); rob@76: } rob@76: rob@76: return USER_STATUS_OK; rob@76: } rob@76: